package com.easyaop.api.spring;

import com.easyaop.api.Arounds;
import com.easyaop.api.LoggerFactory;
import com.easyaop.api.advisor.AspectjExpressionPointcut;
import com.easyaop.api.around.Call;
import com.easyaop.api.join.JoinPoint;
import com.easyaop.core.AroundsImpl;
import lombok.Lombok;
import org.aopalliance.aop.Advice;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.PerClauseKind;
import org.aspectj.weaver.tools.JoinPointMatch;
import org.springframework.aop.Advisor;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
import org.springframework.aop.aspectj.AspectJAfterAdvice;
import org.springframework.aop.aspectj.AspectJAfterThrowingAdvice;
import org.springframework.aop.aspectj.SimpleAspectInstanceFactory;
import org.springframework.aop.aspectj.annotation.*;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

/**
 * @author timecarol 2025/4/14 09:08
 */
public final class AspectJAdvisorUtils {

    private AspectJAdvisorUtils() {
    }

    private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[]{
            Pointcut.class,
            Around.class,
            Before.class,
            After.class,
            AfterReturning.class,
            AfterThrowing.class
    };

    /**
     * Cache for singleton aspect instances.
     */
    private static final Map<Class<?>, Object> aspectCache = new ConcurrentHashMap<>();

    private static final Logger logger = LoggerFactory.getLogger(AspectJAdvisorUtils.class.getName());

    /**
     * 检查类上是否标注了 {@link Aspect}
     *
     * @param clazz 需要检查的类
     * @return 存在注解返回true, 否则返回false
     */
    public static boolean hasAspectAnnotation(Class<?> clazz) {
        return Objects.nonNull(clazz) && Objects.nonNull(AnnotationUtils.findAnnotation(clazz, Aspect.class));
    }

    /**
     * 将 aspect 类添加到 easy-aop 中
     *
     * @param aspectClass aspect 类
     */
    public static void addAspect(Class<?> aspectClass) {
        if (!hasAspectAnnotation(aspectClass)) return;

        //校验 Aspect 类不能是抽象方法和接口
        if (aspectClass.isInterface() || Modifier.isAbstract(aspectClass.getModifiers())) return;

        String aspectName = aspectClass.getName();
        Object aspectInstance = BeanUtils.instantiateClass(aspectClass);
        final AspectJAdvisorFactory aspectFactory = new ReflectiveAspectJAdvisorFactory();
        List<Advisor> advisors = aspectFactory.getAdvisors(createAspectInstanceFactory(aspectClass, aspectName));

        Arounds arounds = AroundsImpl.getInstance();

        for (Advisor advisor : advisors) {
            Advice advice = advisor.getAdvice();
            if (!(advice instanceof AbstractAspectJAdvice)) continue;
            AbstractAspectJAdvice aspectJAdvice = (AbstractAspectJAdvice) advice;

            Method getJoinPointMatchMethod = ReflectionUtils.findMethod(AbstractAspectJAdvice.class, "getJoinPointMatch");
            if (Objects.nonNull(getJoinPointMatchMethod)) ReflectionUtils.makeAccessible(getJoinPointMatchMethod);

            Method invokeAdviceMethod = ReflectionUtils.findMethod(AbstractAspectJAdvice.class, "invokeAdviceMethod", JoinPointMatch.class, Object.class, Throwable.class);
            if (Objects.nonNull(invokeAdviceMethod)) ReflectionUtils.makeAccessible(invokeAdviceMethod);

            arounds.add(AspectjExpressionPointcut.of(aspectJAdvice.getPointcut().getPointcutExpression().getPointcutExpression()), new com.easyaop.api.around.Around() {
                /**
                 * {@inheritDoc}
                 */
                @Override
                public Object around(Object target, Object[] args, JoinPoint joinPoint, Call call) throws Exception {
                    ReflectionUtils.findMethod(ClassUtils.getUserClass(aspectJAdvice), "getJoinPointMatch");

                    Method invokeAdviceMethod = ReflectionUtils.findMethod(ClassUtils.getUserClass(aspectJAdvice), "invokeAdvice");

                    try {
                        if (advice instanceof MethodBeforeAdvice) {
                            ((MethodBeforeAdvice) advice).before(joinPoint.getJavaMethod(), args, target);
                        }
                        Object res = call.call();
                        if (advice instanceof AfterReturningAdvice) {
                            ((AfterReturningAdvice) advice).afterReturning(res, joinPoint.getJavaMethod(), args, target);
                        }
                        return res;
                    } catch (Throwable t) {
                        if (advice instanceof AspectJAfterThrowingAdvice && Objects.nonNull(getJoinPointMatchMethod) && Objects.nonNull(invokeAdviceMethod)) {
                            JoinPointMatch joinPointMatch = (JoinPointMatch) ReflectionUtils.invokeMethod(getJoinPointMatchMethod, advice);
                            ReflectionUtils.invokeMethod(invokeAdviceMethod, advice, joinPointMatch, null, t);
                        }
                        throw Lombok.sneakyThrow(t);
                    } finally {
                        if (advice instanceof AspectJAfterAdvice && Objects.nonNull(getJoinPointMatchMethod) && Objects.nonNull(invokeAdviceMethod)) {
                            JoinPointMatch joinPointMatch = (JoinPointMatch) ReflectionUtils.invokeMethod(getJoinPointMatchMethod, advice);
                            ReflectionUtils.invokeMethod(invokeAdviceMethod, advice, joinPointMatch, null, null);
                        }
                    }
                }
            });

        }

    }

    /**
     * 创建一个 {@link MetadataAwareAspectInstanceFactory}
     */
    private static MetadataAwareAspectInstanceFactory createAspectInstanceFactory(Class<?> aspectClass, String aspectName) {
        AspectMetadata am = new AspectMetadata(aspectClass, aspectName);
        if (!am.getAjType().isAspect()) {
            throw new IllegalArgumentException("Class [" + aspectClass.getName() + "] is not a valid aspect type");
        }
        MetadataAwareAspectInstanceFactory instanceFactory;
        if (am.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
            // Create a shared aspect instance.
            Object instance = aspectCache.computeIfAbsent(aspectClass, clazz -> new SimpleAspectInstanceFactory(clazz).getAspectInstance());
            instanceFactory = new SingletonMetadataAwareAspectInstanceFactory(instance, aspectName);
        } else {
            // Create a factory for independent aspect instances.
            instanceFactory = new SimpleMetadataAwareAspectInstanceFactory(aspectClass, aspectName);
        }
        return instanceFactory;
    }

}
